﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace Orz.Framework.Foundation.Behaviors
{
    /// <summary>
    /// Enable TextBlock show tooltip with full text when ellipsing
    /// </summary>
    public class EllipseBehavior : System.Windows.Interactivity.Behavior<TextBlock>
    {
        #region Field

        MethodInfo m_GetLineDetails = typeof(TextBlock).GetMethod("GetLineDetails",
              BindingFlags.NonPublic | BindingFlags.Instance);
        object[] args = new object[] { 0, 0, 0, 0, 0 };
        #endregion

        #region DependencyProperty

        /// <summary>
        /// Gets or sets a value indicating whether [auto show tooltip when ellipsed].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [auto show tooltip when ellipsed]; otherwise, <c>false</c>.
        /// </value>
        public bool AutoShowTooltipWhenEllipsed
        {
            get { return (bool)GetValue(AutoShowTooltipWhenEllipsedProperty); }
            set { SetValue(AutoShowTooltipWhenEllipsedProperty, value); }
        }
        // Using a DependencyProperty as the backing store for IsShowTooltipWhenEllipsed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoShowTooltipWhenEllipsedProperty =
            DependencyProperty.Register("AutoShowTooltipWhenEllipsed", typeof(bool), typeof(EllipseBehavior), new UIPropertyMetadata(true));

        #endregion

        #region Override

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            base.OnAttached();

            if (AutoShowTooltipWhenEllipsed && this.AssociatedObject.TextTrimming == TextTrimming.None)
                this.AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
            //Register events
            var dpDesc = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
            dpDesc.AddValueChanged(this.AssociatedObject, OnTextChanged);
            this.AssociatedObject.Loaded += new RoutedEventHandler(OnLoaded);
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            //unload events
            this.AssociatedObject.Loaded -= new RoutedEventHandler(OnLoaded);
            this.AssociatedObject.SizeChanged -= new SizeChangedEventHandler(OnSizeChanged);
            var dpDesc = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
            dpDesc.RemoveValueChanged(this.AssociatedObject, OnTextChanged);
        }

        #endregion

        #region EventHandler

        /// <summary>
        /// Called when [size changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetToolTip(null);
        }

        /// <summary>
        /// Called when AssociatedObject [loaded].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        void OnLoaded(object sender, RoutedEventArgs e)
        {
            SetToolTip(null);
            //Register SizeChanged event
            this.AssociatedObject.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        /// <summary>
        /// Overrides the <see cref="T:System.Windows.DependencyObject"/> implementation of <see cref="M:System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)"/> to also invoke any <see cref="E:System.Windows.Freezable.Changed"/> handlers in response to a changing dependency property of type <see cref="T:System.Windows.Freezable"/>.
        /// </summary>
        /// <param name="e">Event data that contains information about which property changed, and its old and new values.</param>
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (this.AssociatedObject != null && e.Property == AutoShowTooltipWhenEllipsedProperty)
            {
                if ((bool)e.NewValue && this.AssociatedObject.TextTrimming == TextTrimming.None)
                    this.AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
                // if AssociatedObject is loaded, update tooltip
                if (this.AssociatedObject.IsLoaded)
                    SetToolTip(null);
            }
        }

        /// <summary>
        /// Called when AssociatedObject [text changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnTextChanged(object sender, EventArgs e)
        {
            Dispatcher.Invoke(DispatcherPriority.Background, (Action<object>)SetToolTip, null);
        }
        #endregion

        #region Method

        /// <summary>
        /// Sets the tool tip.
        /// </summary>
        /// <param name="obj">The obj.</param>
        private void SetToolTip(object obj)
        {
            if (AutoShowTooltipWhenEllipsed && this.AssociatedObject.TextTrimming != TextTrimming.None)
            {
                TextBlock tb = this.AssociatedObject;
                m_GetLineDetails.Invoke(tb, args);
                if ((int)args[4] > 0)
                {
                    tb.ToolTip = tb.Text;
                }
                else
                {
                    tb.ToolTip = null;
                }
            }
        }
        #endregion
    }
}